<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title></title>
  <style>
    body {
      margin: 0;
    }

    .section {
      /* padding: 20px; */
    }

    .selection-start,
    .selection-end {
      /* background-color: yellow;
      font-weight: bold; */
      /* background-color: #5254ff; */
      /* padding: 0 5px; */
    }

    .highlight-canvas {
      position: absolute;
      pointer-events: none;
      /* 确保 canvas 不影响页面交互 */
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
    }

    .section_box {
      padding: 40px;
    }
    .canvasF {
      position: relative;
    }
  </style>
</head>

<body>
  <div class="section_box">
    
    
    <div class="section canvasF" contenteditable="true">
      <canvas id="selectionCanvas" class="highlight-canvas"></canvas>

      <div class="section" id="A469aa71d49094a6caea2ac735abb2b41">
        <h2 class="title_disable">1.2 系统概述</h2>
        <div class="child">
          <div class="para">
            本条应概述本文档适用的系统和软件的用途：描述系统和软件的一般特性(如规模、安全性、可靠性、实时性、技术风险等特性):概述系统开发、运行和维护的历史；标识项目的需方、用户、开发方和保障机构等；标识当前和计划的运行现场；列出其他有关文档。本条应描述在系统合格性测试的独立性、在目标计算机系统上进行测试、系统合格性测试准备、系统合格性测试的预演、系统合格性测试的执行、修改和回归测试、分析与记录系统合格性测试结果等方面参与系统合格性测试所遵循的途径。本条应描述适用于本项目的资源，可包括：
            人力资源，应包括：此项目投入的人力估计(人时数);按职责(如：管理，软件工程，软件测试，软件配置管理，软件产品评价，软件质量保证
            等)所投入人力的分解结构；履行每个职责的人员的技术能力、地理位置和涉密程度的分解结构。为适应合同中的工作，开发人员工作的地理位置、要使用的设施、保密区域等概况。合同中工作需要的、且由需方提供的设备、软件、服务、文档、数据及设施，并给出何时需上述各项的进度表。其他所需的资源，包括：获得资源的计划、需要的日期、每个资源项的可用性(就绪的时间)。
          </div>
          <div class="tableCaption">表 1 测试表</div>
        </div>
      </div>
    </div>
  </div>
  

</body>
<script>
  let uuid = '';
  const selectionBoxes = []; // 存储所有选区
  function generateUniqueId() {
    const timestamp = Date.now().toString(16); // 当前时间戳转16进制（8位）
    const randomPart = Math.floor(Math.random() * 10000000).toString().padStart(8, '0'); // 8位随机数
    return 'A' + randomPart + timestamp; // 总长16位
  }
  // return
  document.addEventListener('mouseup', () => {
    const selection = window.getSelection();
    if (!selection.rangeCount) return;

    const range = selection.getRangeAt(0);
    const selectedText = range.toString().trim(); // 获取选区内容并去除前后空格

    // **新增逻辑**：如果没有选中文字，直接返回，不执行插入
    if (selectedText === '') return;

    // 先移除已有的标记，避免重复插入
    // document.querySelectorAll('.selection-start, .selection-end').forEach(el => el.remove());

    uuid = generateUniqueId();

    const startMarker = document.createElement('span');
    startMarker.className = 'selection-start';
    startMarker.id = uuid + '_start'; // 
    // startMarker.textContent = '[start]'; // 可视化标记

    const endMarker = document.createElement('span');
    endMarker.className = 'selection-end';
    endMarker.id = uuid + '_end'; // 
    // endMarker.textContent = '[end]'; // 可视化标记

    // 在起始位置插入标签
    range.insertNode(startMarker);

    // 重新获取 selection 以确保正确定位
    selection.removeAllRanges();
    selection.addRange(range);

    // 在结束位置插入标记
    range.collapse(false); // 移动光标到 range 末尾
    range.insertNode(endMarker);

    drawSelectionBox(uuid + "_start", uuid + "_end");
  });

  // 监听窗口变化，重绘所有框
  // window.addEventListener("resize", () => {
  //   const ctx = document.getElementById("selectionCanvas").getContext("2d");
  //   ctx.canvas.width = document.querySelector(".section").offsetWidth;
  //   ctx.canvas.height = document.querySelector(".section").offsetHeight;
  //   redrawSelectionBoxes(ctx);
  // });
 


  function drawSelectionBox(startId, endId) {
    const startEl = document.getElementById(startId);
    const endEl = document.getElementById(endId);
    if (!startEl || !endEl) return;

    const startRect = startEl.getBoundingClientRect();
    const endRect = endEl.getBoundingClientRect();

    const container = document.querySelector(".section");
    const containerRect = container.getBoundingClientRect();

    const canvas = document.getElementById("selectionCanvas");
    canvas.width = container.offsetWidth;
    canvas.height = container.offsetHeight;
    const ctx = canvas.getContext("2d");

    // 计算相对坐标（基于 container）
    const startX = startRect.left - containerRect.left;
    const startY = startRect.top - containerRect.top;
    const endX = endRect.left - containerRect.left;
    const endY = endRect.top - containerRect.top;
    const lineHeight = startRect.height;

    // 存储选区信息
    selectionBoxes.push({ startX, startY, endX, endY, lineHeight });

    redrawSelectionBoxes(ctx);
  }

  function redrawSelectionBoxes(ctx) {
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // 清除画布

    ctx.strokeStyle = "#5254ff";
    ctx.lineWidth = 1;

    selectionBoxes.forEach(({ startX, startY, endX, endY, lineHeight }) => {
      if (startY === endY) {
        // 同一行
        ctx.strokeRect(startX, startY, endX - startX, lineHeight);
      } else {
        // 跨行
        const totalWidth = ctx.canvas.width;
        ctx.strokeRect(startX, startY, totalWidth - startX, lineHeight);
        let currentY = startY + lineHeight;
        while (currentY + lineHeight < endY) {
          ctx.strokeRect(0, currentY, totalWidth, lineHeight);
          currentY += lineHeight;
        }
        ctx.strokeRect(0, endY, endX, lineHeight);
      }
    });
  }
  // function drawSelectionBox(startId, endId) {
  //   console.log(startId, endId);

  //   const startEl = document.getElementById(startId);
  //   const endEl = document.getElementById(endId);
  //   if (!startEl || !endEl) return;

  //   // 获取元素的位置信息
  //   const startRect = startEl.getBoundingClientRect();
  //   const endRect = endEl.getBoundingClientRect();

  //   // 获取父级容器（一般是 contenteditable 的父元素）
  //   const container = document.querySelector(".section");
  //   const containerRect = container.getBoundingClientRect();

  //   // 获取 canvas 并调整大小
  //   const canvas = document.getElementById("selectionCanvas");
  //   canvas.width = container.offsetWidth;
  //   canvas.height = container.offsetHeight;
  //   const ctx = canvas.getContext("2d");

  //   // 计算相对坐标（基于 container 位置）
  //   const startX = startRect.left - containerRect.left;
  //   const startY = startRect.top - containerRect.top;
  //   const endX = endRect.left - containerRect.left;
  //   const endY = endRect.top - containerRect.top;
  //   const lineHeight = startRect.height; // 计算行高

  //   // ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除之前的绘制
  //   ctx.strokeStyle = "#5254ff"; // 设置蓝色边框
  //   ctx.lineWidth = 1;

  //   if (startY === endY) {
  //     // *** 1️⃣ 同一行 ***
  //     ctx.strokeRect(startX, startY, endX - startX, lineHeight);
  //   } else {
  //     // *** 2️⃣ 跨多行 ***
  //     const totalWidth = container.offsetWidth; // 计算整行宽度

  //     // 第一行（起始行）
  //     ctx.strokeRect(startX, startY, totalWidth - startX, lineHeight);

  //     // 中间行（整行覆盖）
  //     let currentY = startY + lineHeight;
  //     while (currentY + lineHeight < endY) {
  //       ctx.strokeRect(0, currentY, totalWidth, lineHeight);
  //       currentY += lineHeight;
  //     }

  //     // 最后一行
  //     ctx.strokeRect(0, endY, endX, lineHeight);
  //   }
  // }


  document.addEventListener('keydown', (event) => {
    if (event.key === 'Backspace') {
      const selection = window.getSelection();
      if (!selection.rangeCount) return false;
      const range = selection.getRangeAt(0);
      let currentNode = range.startContainer; // 当前光标所在的节点
      // 如果当前节点是文本节点，获取其父级
      if (currentNode.nodeType === Node.TEXT_NODE) {
        currentNode = currentNode.parentNode;
      }
      // 获取最近的 `.child` 父级
      const childParent = currentNode.closest('.child');
      if (!childParent) return false; // 不是 .child 内部，不处理
      // 检查是否光标位于 .child 内部的最前面
      const isAtStart = isCaretAtStart(range, childParent);
      console.log(1, isAtStart, childParent);
      event.preventDefault();
      if (isAtStart) {
        console.warn('禁止删除：光标已在 .child 开头');
        event.preventDefault();
      }
    }
  });

  /**
 * 判断光标是否在指定元素（.child）内的开头
 * @param {Range} range - 选区范围对象
 * @param {HTMLElement} parentElement - 目标父级（.child）
 * @returns {boolean} - 是否光标在 .child 内的最前面
 */
  function isCaretAtStart(range, parentElement) {
    let startContainer = range.startContainer;
    let offset = range.startOffset;

    // 处理空白文本（如果当前节点是全是空格的文本节点）
    while (startContainer.nodeType === Node.TEXT_NODE && startContainer.textContent.trim() === '') {
      startContainer = startContainer.previousSibling || startContainer.parentNode;
      if (!startContainer || startContainer === parentElement) return true; // 没有找到可见文本
    }
    // 如果当前是文本节点，检查光标左侧是否有内容（排除纯空格）
    if (startContainer.nodeType === Node.TEXT_NODE) {
      const leftText = startContainer.textContent.slice(0, offset);
      if (leftText.trim().length > 0) {
        return false; // 前面有内容，允许删除
      }
    }
    // 遍历前面的兄弟节点，看看是否有可见文本
    let node = startContainer;
    while (node && node !== parentElement) {
      if (node.previousSibling) {
        node = node.previousSibling;
        // 如果前面有非空的文本或可见元素，允许删除
        if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== '') {
          return false;
        }
        if (node.nodeType === Node.ELEMENT_NODE && node.innerText.trim() !== '') {
          return false;
        }
      } else {
        node = node.parentNode;
      }
    }
    return true; // 说明前面没有任何内容，禁止删除
  }

</script>

</html>